package utils

import (
	"fmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/event"
	mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl"
	"github.com/pkg/errors"
	packager "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/gopackager"
	"strings"
)

type FabricSetup struct {
	// 配置文件只有一个
	ConfigFile 	string
	OrgId 		string
	OrdererID	string
	ChannelID	string
	ChaincodeID string
	ChaincodeVersion string
	initialized bool

	// 一个节点下，可以有多个通道，每个通道下可能有多个链码，so， 这里需要重新整理
	ChannelConfig string
	ChaincodeGoPath string
	ChaincodePath  string

	// 一个节点只能有一个身份，所以下面的参数只能是一个
	OrgAdmin string
	OrgName string
	UserName string
	DefaultPeer string

	client *channel.Client
	admin  *resmgmt.Client
	sdk    *fabsdk.FabricSDK
	event *event.Client

	identity msp.SigningIdentity
}

func (setup *FabricSetup) Initialize() {

	if setup.initialized {
		panic("sdk already initialized\n\t")
		//return errors.New("sdk already initialized\n\t")
	}

	// 加载配置文件
	sdk, err := fabsdk.New(config.FromFile(setup.ConfigFile))
	if err != nil {
		panic(err)
		//return errors.WithMessage(err, "failed to create sdk\n\t")
	}
	setup.sdk = sdk
	fmt.Println("sdk created")

	// 加载用户证书
	resourceManagerClientContext := setup.sdk.Context(fabsdk.WithUser(setup.OrgAdmin), fabsdk.WithOrg(setup.OrgName))
	resMgmtClient, err := resmgmt.New(resourceManagerClientContext)
	if err != nil {
		panic(err)
		//return errors.WithMessage(err, "fail to create channel management client from Admin identity\n\t")
	}
	setup.admin = resMgmtClient
	fmt.Println("Resource management client created")

	//mspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(setup.OrgName))
	//if err != nil {
	//	panic(err)
	//	//return errors.WithMessage(err, "failed to create MSP client")
	//}
	//setup.identity, err = mspClient.GetSigningIdentity(setup.OrgAdmin)
	//if err != nil {
	//	panic(err)
	//	//return errors.WithMessage(err, "failed to get admin signing identity")
	//}


	// 初始化通道客户端信息，用于执行交易
	clientContext := setup.sdk.ChannelContext(setup.ChannelID, fabsdk.WithUser(setup.UserName))
	setup.client, err = channel.New(clientContext)		// todo error
	if err != nil {
		panic(err)
		//return errors.WithMessage(err, "failed to create new channel client\n\t")
	}
	fmt.Println("Channel client created")

	//setup.event, err = event.New(clientContext)
	//if err != nil {
	//	panic(err)
	//	//return errors.WithMessage(err, "failed to create new event client")
	//}
	//fmt.Println("Event client created")

	setup.initialized = true
}

/**
	查询链码
 */
func (setup *FabricSetup) Query(chaincodeId, fcn string, args []string) (string, error) {
	rargs := BatchStr2Bytes(args)
	fmt.Println(chaincodeId)
	fmt.Println(fcn)
	fmt.Println(args)
	response, err := setup.client.Query(channel.Request{
		ChaincodeID: chaincodeId,
		Fcn: fcn,
		Args: rargs,
	})
	if err != nil {
		return "", fmt.Errorf("failed to query: %v", err)
	}
	return string(response.Payload), nil
}

/**
	执行连码
 */
func (setup *FabricSetup) Invoke(chaincodeId, fcn string, args []string, transientData map[string][]byte) (string, error) {
	rargs := BatchStr2Bytes(args)
	response, err := setup.client.Execute(channel.Request{
		ChaincodeID: chaincodeId,
		Fcn: fcn,
		Args: rargs,
		TransientMap: transientData,
	})
	if err != nil {
		return "", fmt.Errorf("failed to execute %s.%s: %v", chaincodeId, fcn, err)
	}
	return string(response.Payload), nil
}

/**
	构建通道
 */
func (setup *FabricSetup) ConstructChannel(channelID, channelConfigPath string) error {
	// 获取管理员身份信息
	mspClient, err := mspclient.New(setup.sdk.Context(), mspclient.WithOrg(setup.OrgName))
	if err != nil {
		return errors.WithMessage(err, "构建msp客户端失败")
	}
	adminIdentity, err := mspClient.GetSigningIdentity(setup.OrgAdmin)
	if err != nil {
		return errors.WithMessage(err, "failed to get admin signing identity")
	}
	// 构建请求
	req := resmgmt.SaveChannelRequest{
		ChannelID: channelID,
		ChannelConfigPath: channelConfigPath,
		SigningIdentities: []msp.SigningIdentity{adminIdentity},
	}
	// 创建channel
	txID, err := setup.admin.SaveChannel(req, resmgmt.WithOrdererEndpoint(setup.OrdererID))
	if err != nil || txID.TransactionID == "" {
		return errors.WithMessage(err, "创建通道失败")
	}
	fmt.Println("Channel created")
	// 不加入通道，不做任何处理，后续步骤参见：@see JoinChannel
	return nil
}

/**
	加入通道
 */
func (setup *FabricSetup) JoinChannel(channel string) error {
	if err := setup.admin.JoinChannel(channel, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(setup.OrdererID)); err != nil {
		return errors.WithMessage(err, "加入通道失败")
	}
	// TODO 是否将通道存入本地客户端，方便后续使用
	return nil
}

func (setup *FabricSetup) InstallCC() (string, error) {
	ccPkg, err := packager.NewCCPackage(setup.ChaincodePath, setup.ChaincodeGoPath)
	if err != nil {
		return "", errors.WithMessage(err, "failed to create chaincode package")
	}
	fmt.Println("ccPkg created")

	ccHasInstall := false
	// 查询已经安装的链码
	ccInstalledRes, err := setup.admin.QueryInstalledChaincodes(resmgmt.WithTargetEndpoints(setup.DefaultPeer))
	if err != nil {
		return "", errors.WithMessage(err, "failed to query Installed chaincode")
	}

	if ccInstalledRes != nil {
		for _, cc := range ccInstalledRes.Chaincodes {
			if strings.EqualFold(cc.Name, setup.ChaincodeID) {
				ccHasInstall = true
			}
		}
	}
	fmt.Println("ccHasInstall: ", ccHasInstall)

	if ccHasInstall {
		return "", errors.New("chaincode has installed, please check")
	} else {

		installCCReq := resmgmt.InstallCCRequest{
			Name:setup.ChaincodeID,
			Path: setup.ChaincodePath,
			Version: setup.ChaincodeVersion,
			Package: ccPkg,
		}
		response, err := setup.admin.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts))
		if err != nil {
			return "", errors.WithMessage(err, "failed to install chaincode")
		} else {
			return response[0].Info, nil
		}

	}
}

/**
	实例化连码
 */
func (setup *FabricSetup) InstantiateCC(channelId, chaincodeId, path, version string, args [][]byte) (string, error) {
	ccInstantiatedRes, err := setup.admin.QueryInstantiatedChaincodes(chaincodeId, resmgmt.WithTargetEndpoints(setup.DefaultPeer))
	if err != nil {
		return "", errors.WithMessage(err, "query instantiated chaincode error")
	}
	if ccInstantiatedRes != nil && len(ccInstantiatedRes.Chaincodes) > 0 {
		for _, chaincodeInfo := range ccInstantiatedRes.Chaincodes {
			if strings.EqualFold(chaincodeInfo.Name, chaincodeId) {
				return "", errors.New(fmt.Sprintf("chaincode %s has instantiated", chaincodeId))
			}
		}
	}
	// 构建背书策略，具体背书策略参见该方法的同级方法
	ccPolicy := cauthdsl.SignedByMspMember(setup.OrgId)
	// 创建实例化请求
	request := resmgmt.InstantiateCCRequest{
		Name: chaincodeId,
		Path: path,
		Version: version,
		Args: args,
		Policy: ccPolicy,
	}
	resp, err := setup.admin.InstantiateCC(channelId, request)
	if err != nil || resp.TransactionID == "" {
		return "", errors.WithMessage(err, "failed to instantiate the chaincode " + chaincodeId)
	}

	fmt.Printf("chaincode %s instantiate success", chaincodeId)
	return string(resp.TransactionID), nil
}

/**
	关闭sdk
 */
func (setup *FabricSetup) CloseSDK() {
	setup.sdk.Close()
}
